﻿namespace Jsl.FxCop
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// A helper class for testing rules.
    /// </summary>
    public static class RuleTestHelper
    {
        #region Public Methods
        /// <summary>
        /// Runs the rule against the specified assembly.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunAssemblyRule(this BaseIntrospectionRule rule, Assembly assembly)
        {
            return rule.RunAssemblyRule(assembly.GetAssemblyNode());
        }

        /// <summary>
        /// Runs the rule against the specified assembly.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="assemblyNode">The assembly node.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunAssemblyRule(this BaseIntrospectionRule rule, AssemblyNode assemblyNode)
        {
            return rule.RunRuleCheck(r => r.Check(assemblyNode));
        }

        /// <summary>
        /// Runs the rule against the specified method.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="codeToCheck">The code to check.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunMethodRule(this BaseIntrospectionRule rule, Action codeToCheck)
        {
            return rule.RunRuleCheck(r => r.Check(NodeHelper.GetMethod(codeToCheck)));
        }

        /// <summary>
        /// Runs the rule against the specified method.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="codeToCheck">The code to check.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunMethodRule<TValue>(this BaseIntrospectionRule rule, Func<TValue> codeToCheck)
        {
            return rule.RunRuleCheck(r => r.Check(NodeHelper.GetMethod(codeToCheck)));
        }

        /// <summary>
        /// Runs the against the specified method.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="method">The method.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunMethodRule(this BaseIntrospectionRule rule, Method method)
        {
            return rule.RunRuleCheck(r => r.Check(method));
        }

        /// <summary>
        /// Runs the rule against the specified method.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunMethodRule(this BaseIntrospectionRule rule, Type targetType, string methodName)
        {
            return rule.RunMethodRule(targetType.GetTypeNode(), methodName);
        }

        /// <summary>
        /// Runs the rule against the specified method.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="targetTypeNode">Type of the target.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunMethodRule(this BaseIntrospectionRule rule, TypeNode targetTypeNode, string methodName)
        {
            if (targetTypeNode == null)
            {
                throw new ArgumentNullException("targetTypeNode");
            }

            var methodNode =
                (from method in targetTypeNode.Members.OfType<Method>()
                 where method.Name.Name == methodName
                 select method).Single();

            return rule.RunRuleCheck(r => r.Check((Member)methodNode));
        }

        /// <summary>
        /// Runs the rule for the specified target.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="rule">The rule to test.</param>
        /// <param name="expression">The expression.</param>
        /// <returns>The problems found, if any.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By desing.")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "By design.")]
        [DebuggerStepThrough]
        public static ProblemCollection RunRule<TResult>(this BaseIntrospectionRule rule, Expression<Func<TResult>> expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            var memberExpression = expression.Body as MemberExpression;
            if (memberExpression != null)
            {
                // Process if this is a property.
                var propertyInfo = memberExpression.Member as PropertyInfo;
                if (propertyInfo != null)
                {
                    return rule.RunPropertyRule(propertyInfo.DeclaringType, propertyInfo.Name);
                }

                // Process if this is a field.
                var fieldInfo = memberExpression.Member as FieldInfo;
                if (fieldInfo != null)
                {
                    var fieldNode =
                        (from field in fieldInfo.DeclaringType.GetFieldNodes()
                         where field.Name.Name == fieldInfo.Name
                         select field).Single();

                    return rule.RunRuleCheck(r => r.Check((Member)fieldNode));
                }
            }

            return null;
        }

        /// <summary>
        /// Runs the field rule against all the fields in the type.
        /// </summary>
        /// <param name="rule">The rule to test.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunRuleForAllFields(this BaseIntrospectionRule rule, Type targetType)
        {
            return
                rule.RunRuleCheck(
                    r =>
                    {
                        // Run check field for each field in the type.
                        TypeNode targetTypeNode = targetType.GetTypeNode();
                        foreach (Field field in targetTypeNode.Members.OfType<Field>())
                        {
                            r.Check(field);
                        }

                        // All the problems will pile up in the rule so all we have to do is return the collection.
                        return r.Problems;
                    });
        }

        /// <summary>
        /// Runs the rule against the specified field.
        /// </summary>
        /// <param name="rule">The rule to test.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunFieldRule(this BaseIntrospectionRule rule, Type targetType, string fieldName)
        {
            return rule.RunFieldRule(targetType.GetTypeNode(), fieldName);
        }

        /// <summary>
        /// Runs the rule against the specified field.
        /// </summary>
        /// <param name="rule">The rule to test.</param>
        /// <param name="targetTypeNode">Type of the target.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunFieldRule(this BaseIntrospectionRule rule, TypeNode targetTypeNode, string fieldName)
        {
            if (targetTypeNode == null)
            {
                throw new ArgumentNullException("targetTypeNode");
            }

            Field targetField = targetTypeNode.GetField(Identifier.For(fieldName));
            return rule.RunRuleCheck(r => r.Check((Member)targetField));
        }

        /// <summary>
        /// Runs the rule against the specified property.
        /// </summary>
        /// <param name="rule">The rule to test.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunPropertyRule(this BaseIntrospectionRule rule, Type targetType, string propertyName)
        {
            var propertyNode =
                (from property in targetType.GetPropertyNodes()
                 where property.Name.Name == propertyName
                 select property).Single();

            return rule.RunRuleCheck(r => r.Check((Member)propertyNode));
        }

        /// <summary>
        /// Runs the rule check.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="runCheck">The run check.</param>
        /// <returns>The problems found, if any.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunRuleCheck(
            this BaseIntrospectionRule rule,
            Func<BaseIntrospectionRule, ProblemCollection> runCheck)
        {
            if (rule == null)
            {
                throw new ArgumentNullException("rule");
            }

            if (runCheck == null)
            {
                throw new ArgumentNullException("runCheck");
            }

            rule.BeforeAnalysis();
            var problems = runCheck(rule);
            rule.AfterAnalysis();

            return problems;
        }

        /// <summary>
        /// Runs the type rule.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunTypeRule(this BaseIntrospectionRule rule, Type targetType)
        {
            return rule.RunTypeRule(targetType.GetTypeNode());
        }

        /// <summary>
        /// Runs the type rule.
        /// </summary>
        /// <param name="rule">The rule to run.</param>
        /// <param name="targetTypeNode">Type of the target.</param>
        /// <returns>The problems found.</returns>
        [DebuggerStepThrough]
        public static ProblemCollection RunTypeRule(this BaseIntrospectionRule rule, TypeNode targetTypeNode)
        {
            return rule.RunRuleCheck(r => r.Check(targetTypeNode));
        }
        #endregion Public Methods
    }
}